//selection
//$Id: selection.h 395 2010-03-26 14:01:03Z Oleg.Bulychov $
#pragma once

#include <limits>
#include "src/heo/include/random.h"
#include "src/heo/include/type_util.h"
#include "src/heo/include/optimization_direction.h"
//__________________________________________________________________________________
//:obsolete, TODO kill
enum
{
    SELECTION_TOURNAMENT,
    SELECTION_ROULETTE,
    SELECTION_RANDOM,
};
//__________________________________________________________________________________
template<class D>
class Selection_Random
{
public:
    typedef D TDirection;

    template<class T>
    void init(T** list, int size)
    {}

    template<class T, class C>
    T* select_One(T** list, int size, C const& cfg, uniform_random& rnd)
    {
        ASSERT_(size > 0);
        int best_index = rnd.next_UInt(size);
        return list[best_index];
    }

    template<class T, class C>
    void select(T** dst, int dst_size, T** src, int src_size, C const& cfg, uniform_random& rnd)
    {
//#pragma omp parallel for
        for (int i = 0; i < dst_size; ++i)
        {
            T* pt = select_One(src, src_size, cfg, rnd);
            if (dst[i] != pt)
                dst[i]->assign(*pt);
        }
    }
};
//__________________________________________________________________________________
template<class D>
class Selection_Tournament
{
public:
    typedef D TDirection;

    template<class T>
    void init(T** list, int size)
    {}

    template<class T, class C>
    T* select_One(T** list, int size, C const& cfg, uniform_random& rnd)
    {
        ASSERT_(size > 0);
        //TODO kill:
        ASSERT_(cfg.tournament_size_);
        int best_index = rnd.next_UInt(size);
        //int best_fitness = list[best_index]->get_Fitness();
        typename T::TCostType best_fitness = list[best_index]->get_Fitness(); // added
        for (int i = 0; i < cfg.tournament_size_ - 1; ++i)
        {
            int j = rnd.next_UInt(size);
            typename T::TCostType f = list[j]->get_Fitness(); // added
            if (TDirection()(f, best_fitness))
            {
                best_fitness = f;
                best_index = j;
            }
        }
        return list[best_index];
    }

    template<class T, class C>
    void select(T** dst, int dst_size, T** src, int src_size, C const& cfg, uniform_random& rnd)
    {
//#pragma omp parallel for
        for (int i = 0; i < dst_size; ++i)
        {
            T* pt = select_One(src, src_size, cfg, rnd);
            if (dst[i] != pt)
                dst[i]->assign(*pt);
        }
    }
};
//__________________________________________________________________________________
template<class D>
class Selection_Roulette
{
    typedef D TDirection;

    template<class T>
    void init_Wheel(T** list, int size, meta::int2type<OPT_MINIMIZE>)
    {
        double overall = 0.;//:prepare roulette wheel
        int zero_count = 0;
//#pragma omp parallel for reduction (+ : overall, zero_count)
        for (int i = 0; i < size; ++i)
        {
            double f = double(list[i]->get_Fitness());
            if (f)
            {
                f = 1. / f;
                list[i]->wheel_sector_ = f;
                overall += f;
            }
            else
            {
                ++zero_count;
            }
        }
        if (zero_count)
        {
            double zero_sector = std::numeric_limits<double>::max() / zero_count;
            zero_sector -= overall;
            overall = std::numeric_limits<double>::max();
//#pragma omp parallel for
            for (int i = 0; i < size; ++i)
            {
                if (!double(list[i]->get_Fitness()))
                {
                    list[i]->wheel_sector_ = zero_sector;
                }
            }
        }
        end_Init_Wheel(list, size, overall);
    }

    template<class T>
    void init_Wheel(T** list, int size, meta::int2type<OPT_MAXIMIZE>)
    {
        double overall = 0.;//:prepare roulette wheel
//#pragma omp parallel for reduction (+ : overall)
        for (int i = 0; i < size; ++i)
        {
            list[i]->wheel_sector_ = double(list[i]->get_Fitness());
            overall += list[i]->wheel_sector_;
        }
        end_Init_Wheel(list, size, overall);
    }

    template<class T>
    void end_Init_Wheel(T** list, int size, double overall)
    {
        if (0. == overall)
        {//:all are bad
            double f = 1. / size;
            for (int i = 0; i < size; ++i)
            {
                list[i]->wheel_sector_ = f * (i + 1);
            }
        }
        else
        {
            double prev = 0.;
            for (int i = 0; i < size; ++i)
            {
                list[i]->wheel_sector_ = prev + (list[i]->wheel_sector_ / overall);
                prev = list[i]->wheel_sector_;
            }
        }
    }

public:
    template<class T>
    void init(T** list, int size)
    {
        init_Wheel(list, size, meta::int2type<TDirection::direction>());
    }

    template<class T, class C>
    T* select_One(T** list, int size, C const& cfg, uniform_random& rnd)
    {
        ////__asm { int 3 };
        ASSERT_(size > 0);
        double r = rnd.next_Double();
        int sel = 0;
        while (r > list[sel]->wheel_sector_)
            ++sel;
        ASSERT_(sel < size);
        return list[sel % size];
    }

    template<class T, class C>
    void select(T** dst, int dst_size, T** src, int src_size, C const& cfg, uniform_random& rnd)
    {
//#pragma omp parallel for
        for (int i = 0; i < dst_size; ++i)
        {
            T* pt = select_One(src, src_size, cfg, rnd);
            if (dst[i] != pt)
                dst[i]->assign(*pt);
        }
    }

};
//__________________________________________________________________________________

//EOF!
